package mxmf.diff;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.util.AntPathMatcher;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.DiffFlags;
import com.flipkart.zjsonpatch.JsonDiff;

public class ContentDiff {

    private static final String FIELD_OP = "op";
    private static final String FIELD_PATH = "path";
    private static final String FIELD_FROM_VALUE = "fromValue";

    private static final String OPERATION_ADD = "add";
    private static final String OPERATION_REMOVE = "remove";
    private static final String OPERATION_REPLACE = "replace";

    private ObjectMapper mapper = new ObjectMapper();

    private List<String> pathRegexs = new ArrayList<>();

    private AntPathMatcher mathcher = new AntPathMatcher();

    private EnumSet<DiffFlags> diffFlags;

    public ContentDiff() {
        pathRegexs.add("/pictures/*/code");
        diffFlags = EnumSet.of(DiffFlags.OMIT_COPY_OPERATION,
                DiffFlags.ADD_ORIGINAL_VALUE_ON_REPLACE, DiffFlags.OMIT_MOVE_OPERATION);
    }

    public List<DiffInfo> diff(Reader newReader, Reader oldReader) throws IOException {

        JsonNode newTree = mapper.readTree(newReader);
        JsonNode oldTree = mapper.readTree(oldReader);
        return diff(newTree, oldTree);
    }

    public List<DiffInfo> diff(String newContent, String oldContent) throws IOException {
        JsonNode newTree = mapper.readTree(newContent);
        JsonNode oldTree = mapper.readTree(oldContent);
        return diff(newTree, oldTree);
    }

    private List<DiffInfo> diff(JsonNode newTree, JsonNode oldTree) throws IOException {
        JsonNode diffJson = JsonDiff.asJson(oldTree, newTree, diffFlags);

        List<DiffInfo> diffInfos = mapper.readValue(diffJson.toString(),
                new TypeReference<List<DiffInfo>>() {
                });
        List<DiffInfo> diffs2 = new ArrayList<>();
        int diffSize = diffJson.size();
        for (int i = 0; i < diffSize; i++) {
            JsonNode n = diffJson.get(i);
            String op = n.get(FIELD_OP).asText();
            String path = n.get(FIELD_PATH).asText();
            DiffInfo aInfo = diffInfos.get(i);
            if (OPERATION_REPLACE.equals(op)) {

                String fromValue = n.get(FIELD_FROM_VALUE).asText();
                if (isPathMatch(path)) {
                    aInfo.setOp(OPERATION_ADD);
                    aInfo.setFromValue(null);
                    DiffInfo removeDiff = new DiffInfo(OPERATION_REMOVE, path, aInfo.getValue(),
                            fromValue, i);
                    diffs2.add(removeDiff);
                }
                Object oldValue = vistorNode(oldTree, path);
                aInfo.setFromValue(oldValue);

            } else if (op.equals(OPERATION_REMOVE)) {
                Object oldValue = vistorNode(oldTree, path);
                aInfo.setFromValue(oldValue);

            }
        }

        diffs2.sort((x, y) -> y.getIndex() - x.getIndex());

        for (DiffInfo diffInfo : diffs2) {
            int index = diffInfo.getIndex();
            diffInfos.add(index, diffInfo);
        }
        return diffInfos;
    }

    public String vistorNode(JsonNode node, String path) {
        String aPath = StringUtils.substringAfter(path, "/");
        String bPath = StringUtils.substringBefore(aPath, "/");
        JsonNode aNode = null;
        if (NumberUtils.isNumber(bPath)) {
            aNode = node.get(Integer.valueOf(bPath));
        } else {
            aNode = node.get(bPath);
        }
        if (path.endsWith(bPath)) {
            return aNode.asText();
        } else {
            String cPath = StringUtils.substringAfter(path, bPath);
            return vistorNode(aNode, cPath);
        }
    }

    private boolean isPathMatch(String path) {
        for (String exprPath : this.pathRegexs) {
            if (mathcher.match(exprPath, path)) {
                return true;
            }
        }
        return false;
    }

    public void addPathRegex(String pathRegex) {
        pathRegexs.add(pathRegex);
    }
}
